/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

var graphic = require("../../util/graphic");

var echarts = require("../../echarts");

var zrUtil = require("static/plugins/js/zrender/lib/core/util");

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
var nodeOpacityPath = ["itemStyle", "opacity"];
var hoverNodeOpacityPath = ["emphasis", "itemStyle", "opacity"];
var lineOpacityPath = ["lineStyle", "opacity"];
var hoverLineOpacityPath = ["emphasis", "lineStyle", "opacity"];

function getItemOpacity(item, opacityPath) {
    return item.getVisual("opacity") || item.getModel().get(opacityPath);
}

function fadeOutItem(item, opacityPath, opacityRatio) {
    var el = item.getGraphicEl();
    var opacity = getItemOpacity(item, opacityPath);

    if (opacityRatio != null) {
        opacity == null && (opacity = 1);
        opacity *= opacityRatio;
    }

    el.downplay && el.downplay();
    el.traverse(function (child) {
        if (child.type !== "group") {
            child.setStyle("opacity", opacity);
        }
    });
}

function fadeInItem(item, opacityPath) {
    var opacity = getItemOpacity(item, opacityPath);
    var el = item.getGraphicEl();
    el.traverse(function (child) {
        if (child.type !== "group") {
            child.setStyle("opacity", opacity);
        }
    }); // Support emphasis here.

    el.highlight && el.highlight();
}

var SankeyShape = graphic.extendShape({
    shape: {
        x1: 0,
        y1: 0,
        x2: 0,
        y2: 0,
        cpx1: 0,
        cpy1: 0,
        cpx2: 0,
        cpy2: 0,
        extent: 0,
        orient: "",
    },
    buildPath: function (ctx, shape) {
        var extent = shape.extent;
        ctx.moveTo(shape.x1, shape.y1);
        ctx.bezierCurveTo(
            shape.cpx1,
            shape.cpy1,
            shape.cpx2,
            shape.cpy2,
            shape.x2,
            shape.y2
        );

        if (shape.orient === "vertical") {
            ctx.lineTo(shape.x2 + extent, shape.y2);
            ctx.bezierCurveTo(
                shape.cpx2 + extent,
                shape.cpy2,
                shape.cpx1 + extent,
                shape.cpy1,
                shape.x1 + extent,
                shape.y1
            );
        } else {
            ctx.lineTo(shape.x2, shape.y2 + extent);
            ctx.bezierCurveTo(
                shape.cpx2,
                shape.cpy2 + extent,
                shape.cpx1,
                shape.cpy1 + extent,
                shape.x1,
                shape.y1 + extent
            );
        }

        ctx.closePath();
    },
    highlight: function () {
        this.trigger("emphasis");
    },
    downplay: function () {
        this.trigger("normal");
    },
});

var _default = echarts.extendChartView({
    type: "sankey",

    /**
     * @private
     * @type {module:echarts/chart/sankey/SankeySeries}
     */
    _model: null,

    /**
     * @private
     * @type {boolean}
     */
    _focusAdjacencyDisabled: false,
    render: function (seriesModel, ecModel, api) {
        var sankeyView = this;
        var graph = seriesModel.getGraph();
        var group = this.group;
        var layoutInfo = seriesModel.layoutInfo; // view width

        var width = layoutInfo.width; // view height

        var height = layoutInfo.height;
        var nodeData = seriesModel.getData();
        var edgeData = seriesModel.getData("edge");
        var orient = seriesModel.get("orient");
        this._model = seriesModel;
        group.removeAll();
        group.attr("position", [layoutInfo.x, layoutInfo.y]); // generate a bezire Curve for each edge

        graph.eachEdge(function (edge) {
            var curve = new SankeyShape();
            curve.dataIndex = edge.dataIndex;
            curve.seriesIndex = seriesModel.seriesIndex;
            curve.dataType = "edge";
            var lineStyleModel = edge.getModel("lineStyle");
            var curvature = lineStyleModel.get("curveness");
            var n1Layout = edge.node1.getLayout();
            var node1Model = edge.node1.getModel();
            var dragX1 = node1Model.get("localX");
            var dragY1 = node1Model.get("localY");
            var n2Layout = edge.node2.getLayout();
            var node2Model = edge.node2.getModel();
            var dragX2 = node2Model.get("localX");
            var dragY2 = node2Model.get("localY");
            var edgeLayout = edge.getLayout();
            var x1;
            var y1;
            var x2;
            var y2;
            var cpx1;
            var cpy1;
            var cpx2;
            var cpy2;
            curve.shape.extent = Math.max(1, edgeLayout.dy);
            curve.shape.orient = orient;

            if (orient === "vertical") {
                x1 =
                    (dragX1 != null ? dragX1 * width : n1Layout.x) +
                    edgeLayout.sy;
                y1 =
                    (dragY1 != null ? dragY1 * height : n1Layout.y) +
                    n1Layout.dy;
                x2 =
                    (dragX2 != null ? dragX2 * width : n2Layout.x) +
                    edgeLayout.ty;
                y2 = dragY2 != null ? dragY2 * height : n2Layout.y;
                cpx1 = x1;
                cpy1 = y1 * (1 - curvature) + y2 * curvature;
                cpx2 = x2;
                cpy2 = y1 * curvature + y2 * (1 - curvature);
            } else {
                x1 =
                    (dragX1 != null ? dragX1 * width : n1Layout.x) +
                    n1Layout.dx;
                y1 =
                    (dragY1 != null ? dragY1 * height : n1Layout.y) +
                    edgeLayout.sy;
                x2 = dragX2 != null ? dragX2 * width : n2Layout.x;
                y2 =
                    (dragY2 != null ? dragY2 * height : n2Layout.y) +
                    edgeLayout.ty;
                cpx1 = x1 * (1 - curvature) + x2 * curvature;
                cpy1 = y1;
                cpx2 = x1 * curvature + x2 * (1 - curvature);
                cpy2 = y2;
            }

            curve.setShape({
                x1: x1,
                y1: y1,
                x2: x2,
                y2: y2,
                cpx1: cpx1,
                cpy1: cpy1,
                cpx2: cpx2,
                cpy2: cpy2,
            });
            curve.setStyle(lineStyleModel.getItemStyle()); // Special color, use source node color or target node color

            switch (curve.style.fill) {
                case "source":
                    curve.style.fill = edge.node1.getVisual("color");
                    break;

                case "target":
                    curve.style.fill = edge.node2.getVisual("color");
                    break;
            }

            graphic.setHoverStyle(
                curve,
                edge.getModel("emphasis.lineStyle").getItemStyle()
            );
            group.add(curve);
            edgeData.setItemGraphicEl(edge.dataIndex, curve);
        }); // Generate a rect for each node

        graph.eachNode(function (node) {
            var layout = node.getLayout();
            var itemModel = node.getModel();
            var dragX = itemModel.get("localX");
            var dragY = itemModel.get("localY");
            var labelModel = itemModel.getModel("label");
            var labelHoverModel = itemModel.getModel("emphasis.label");
            var rect = new graphic.Rect({
                shape: {
                    x: dragX != null ? dragX * width : layout.x,
                    y: dragY != null ? dragY * height : layout.y,
                    width: layout.dx,
                    height: layout.dy,
                },
                style: itemModel.getModel("itemStyle").getItemStyle(),
            });
            var hoverStyle = node.getModel("emphasis.itemStyle").getItemStyle();
            graphic.setLabelStyle(
                rect.style,
                hoverStyle,
                labelModel,
                labelHoverModel,
                {
                    labelFetcher: seriesModel,
                    labelDataIndex: node.dataIndex,
                    defaultText: node.id,
                    isRectText: true,
                }
            );
            rect.setStyle("fill", node.getVisual("color"));
            graphic.setHoverStyle(rect, hoverStyle);
            group.add(rect);
            nodeData.setItemGraphicEl(node.dataIndex, rect);
            rect.dataType = "node";
        });
        nodeData.eachItemGraphicEl(function (el, dataIndex) {
            var itemModel = nodeData.getItemModel(dataIndex);

            if (itemModel.get("draggable")) {
                el.drift = function (dx, dy) {
                    sankeyView._focusAdjacencyDisabled = true;
                    this.shape.x += dx;
                    this.shape.y += dy;
                    this.dirty();
                    api.dispatchAction({
                        type: "dragNode",
                        seriesId: seriesModel.id,
                        dataIndex: nodeData.getRawIndex(dataIndex),
                        localX: this.shape.x / width,
                        localY: this.shape.y / height,
                    });
                };

                el.ondragend = function () {
                    sankeyView._focusAdjacencyDisabled = false;
                };

                el.draggable = true;
                el.cursor = "move";
            }

            el.highlight = function () {
                this.trigger("emphasis");
            };

            el.downplay = function () {
                this.trigger("normal");
            };

            el.focusNodeAdjHandler &&
                el.off("mouseover", el.focusNodeAdjHandler);
            el.unfocusNodeAdjHandler &&
                el.off("mouseout", el.unfocusNodeAdjHandler);

            if (itemModel.get("focusNodeAdjacency")) {
                el.on(
                    "mouseover",
                    (el.focusNodeAdjHandler = function () {
                        if (!sankeyView._focusAdjacencyDisabled) {
                            sankeyView._clearTimer();

                            api.dispatchAction({
                                type: "focusNodeAdjacency",
                                seriesId: seriesModel.id,
                                dataIndex: el.dataIndex,
                            });
                        }
                    })
                );
                el.on(
                    "mouseout",
                    (el.unfocusNodeAdjHandler = function () {
                        if (!sankeyView._focusAdjacencyDisabled) {
                            sankeyView._dispatchUnfocus(api);
                        }
                    })
                );
            }
        });
        edgeData.eachItemGraphicEl(function (el, dataIndex) {
            var edgeModel = edgeData.getItemModel(dataIndex);
            el.focusNodeAdjHandler &&
                el.off("mouseover", el.focusNodeAdjHandler);
            el.unfocusNodeAdjHandler &&
                el.off("mouseout", el.unfocusNodeAdjHandler);

            if (edgeModel.get("focusNodeAdjacency")) {
                el.on(
                    "mouseover",
                    (el.focusNodeAdjHandler = function () {
                        if (!sankeyView._focusAdjacencyDisabled) {
                            sankeyView._clearTimer();

                            api.dispatchAction({
                                type: "focusNodeAdjacency",
                                seriesId: seriesModel.id,
                                edgeDataIndex: el.dataIndex,
                            });
                        }
                    })
                );
                el.on(
                    "mouseout",
                    (el.unfocusNodeAdjHandler = function () {
                        if (!sankeyView._focusAdjacencyDisabled) {
                            sankeyView._dispatchUnfocus(api);
                        }
                    })
                );
            }
        });

        if (!this._data && seriesModel.get("animation")) {
            group.setClipPath(
                createGridClipShape(
                    group.getBoundingRect(),
                    seriesModel,
                    function () {
                        group.removeClipPath();
                    }
                )
            );
        }

        this._data = seriesModel.getData();
    },
    dispose: function () {
        this._clearTimer();
    },
    _dispatchUnfocus: function (api) {
        var self = this;

        this._clearTimer();

        this._unfocusDelayTimer = setTimeout(function () {
            self._unfocusDelayTimer = null;
            api.dispatchAction({
                type: "unfocusNodeAdjacency",
                seriesId: self._model.id,
            });
        }, 500);
    },
    _clearTimer: function () {
        if (this._unfocusDelayTimer) {
            clearTimeout(this._unfocusDelayTimer);
            this._unfocusDelayTimer = null;
        }
    },
    focusNodeAdjacency: function (seriesModel, ecModel, api, payload) {
        var data = seriesModel.getData();
        var graph = data.graph;
        var dataIndex = payload.dataIndex;
        var itemModel = data.getItemModel(dataIndex);
        var edgeDataIndex = payload.edgeDataIndex;

        if (dataIndex == null && edgeDataIndex == null) {
            return;
        }

        var node = graph.getNodeByIndex(dataIndex);
        var edge = graph.getEdgeByIndex(edgeDataIndex);
        graph.eachNode(function (node) {
            fadeOutItem(node, nodeOpacityPath, 0.1);
        });
        graph.eachEdge(function (edge) {
            fadeOutItem(edge, lineOpacityPath, 0.1);
        });

        if (node) {
            fadeInItem(node, hoverNodeOpacityPath);
            var focusNodeAdj = itemModel.get("focusNodeAdjacency");

            if (focusNodeAdj === "outEdges") {
                zrUtil.each(node.outEdges, function (edge) {
                    if (edge.dataIndex < 0) {
                        return;
                    }

                    fadeInItem(edge, hoverLineOpacityPath);
                    fadeInItem(edge.node2, hoverNodeOpacityPath);
                });
            } else if (focusNodeAdj === "inEdges") {
                zrUtil.each(node.inEdges, function (edge) {
                    if (edge.dataIndex < 0) {
                        return;
                    }

                    fadeInItem(edge, hoverLineOpacityPath);
                    fadeInItem(edge.node1, hoverNodeOpacityPath);
                });
            } else if (focusNodeAdj === "allEdges") {
                zrUtil.each(node.edges, function (edge) {
                    if (edge.dataIndex < 0) {
                        return;
                    }

                    fadeInItem(edge, hoverLineOpacityPath);
                    edge.node1 !== node &&
                        fadeInItem(edge.node1, hoverNodeOpacityPath);
                    edge.node2 !== node &&
                        fadeInItem(edge.node2, hoverNodeOpacityPath);
                });
            }
        }

        if (edge) {
            fadeInItem(edge, hoverLineOpacityPath);
            fadeInItem(edge.node1, hoverNodeOpacityPath);
            fadeInItem(edge.node2, hoverNodeOpacityPath);
        }
    },
    unfocusNodeAdjacency: function (seriesModel, ecModel, api, payload) {
        var graph = seriesModel.getGraph();
        graph.eachNode(function (node) {
            fadeOutItem(node, nodeOpacityPath);
        });
        graph.eachEdge(function (edge) {
            fadeOutItem(edge, lineOpacityPath);
        });
    },
}); // Add animation to the view

function createGridClipShape(rect, seriesModel, cb) {
    var rectEl = new graphic.Rect({
        shape: {
            x: rect.x - 10,
            y: rect.y - 10,
            width: 0,
            height: rect.height + 20,
        },
    });
    graphic.initProps(
        rectEl,
        {
            shape: {
                width: rect.width + 20,
            },
        },
        seriesModel,
        cb
    );
    return rectEl;
}

module.exports = _default;
